/*
 * lab3q17.c
 *
 *  Created on: Feb 26, 2011
 *      Author: Vishal Verma
 */
 
/*
 * PROGRAM DESCRIPTION	
 * 
 * Allocate 'n' buffers of 's' bytes each as desired by user.
 * 
 * Continue to accept data into default buffer, process special commands.
 * Discard silently on full buffer
 *
 * '/' Command:
 * 	- Display current buffer contents in hex
 *	- Switch to next available buffer
 *	- Rotate buffers cyclically
 *
 * '?' Command:
 *	- Display information for all buffers -- start address, size and amount filled
 * 	- Display usage statistics for current buffer -- number of vowels and numeric characters
 *	- Dump contents in ASCII
 * 	- Empty current buffer
 */
 
/*
 * COMPILER FLAGS
 * sdcc -c --debug -mmcs51 --std-sdcc99 --model-large
 *
 * LINKER FLAGS
 * sdcc --debug -mmcs51 --code-loc 0x0000 --code-size 0x8000 --xram-loc 0x0000 --xram-size 0x8000 --model-large --out-fmt-ihx
 *
 * USAGE SPECIFICATIONS:
 * Baud rate - 57600
 */
 
/*==============================================INCLUDES=========================================*/
#include <at89c51ed2.h>  //also includes 8052.h and 8051.h
#include <mcs51reg.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

/*==============================================DEFINES==========================================*/
#define TRUE 1
#define FALSE 0
#define HEAP_SIZE 1800   				// size must be smaller than available XRAM
#define EC_CRIT -1						//currently unused
#define MAXBUFS 5						//max allowed buffers
#define MAXBUFSIZE 1000					//max allowed buffer size
#define MINBUFSIZE 20					//min allowed buffer size
#define STATCNT 16						//number of stats being counted
#define SILENT 1						//used in cleanBuf
#define VERBOSE 0						//used in cleanBuf
#define LIMASCII 50						//used in dispAll
#define LIMHEX 16						//used in dispAll

/*==============================================GLOBALS==========================================*/
unsigned char xdata heap[HEAP_SIZE];

unsigned int numBuf = 0;				//number of buffers
unsigned int sizeBuf = 0;				//size of each buffer
unsigned int curBuf = 0;				//selected buffer
unsigned int chCount[MAXBUFS] = {0};	//number of valid characters stored for each buffer

unsigned int stats[STATCNT] = {0}; 		//indexed by enum STAT


xdata char *buffer[MAXBUFS] = {NULL};	//array of pointers to hold start addresses of each buffer
xdata char *bufPtr = NULL;				//pointer to hold start address of selected buffer
										//used to avoid double indexing on *buffer
										
enum RETCODE{							//retcodes/parameter flags for all functions
	PK_EINPRINT = 5,
	PK_QFLAG,
	PK_SFLAG,
	PK_EINSTOR,
	DISP_HEX,
	DISP_ASCII
};

enum STAT{								//to index into statistics array
	ZERO = 0,
	ONE,
	TWO,
	THREE,
	FOUR,
	FIVE,
	SIX,
	SEVEN,
	EIGHT,
	NINE,
	A,
	E,
	I,
	O,
	U,
	GARBAGE
};

/*=========================================FUNCTION PROTOTYPES===================================*/
 
void putchar (char c);
char getchar (void);
int putstr (char *s);
int getstr (char *s);
void serialInit(int);
void clearScr(void);
void myDelay(int j);
void cleanBuf(int id, int mode);
void initBufs(void);
void calcStats(int id);
void dispAll(int mode, int id);
void initAll(void);
int processKey(void);
void processQflag(void);
void processSflag(void);


/*=========================================FUNCTION DEFINITIONS===================================*/

/*
* NAME :            _sdcc_external_startup
*
* DESCRIPTION :     Will run before main(). Used to initialize registers etc. as required
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	AUXR
*           
* OUTPUTS :
*       GLOBALS:	AUXR
*       RETURN:		-          
*/

_sdcc_external_startup() {
	
	AUXR |= 0x0C;
	return 0;
}

/*
* NAME :            putchar
*
* DESCRIPTION :     print 1 character to serial port
*
* INPUTS :
*       PARAMETERS:	char c
*		GLOBALS:	SCON
*           
* OUTPUTS :
*       GLOBALS :	SBUF, TI
*       RETURN :	-
*        
* PROCESS :
*                   [1]	Wait for port to be ready
* 					[2]	Print char and clear TI	       
*/

void putchar (char c) {
	
	while ((SCON & 0x02) == 0);    				// wait for TX ready, spin on TI
	SBUF = c;  									// load serial port with transmit value
	TI = 0;  									// clear TI flag
}

/*
* NAME :            getchar
*
* DESCRIPTION :     get 1 character from serial port
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	SCON
*           
* OUTPUTS :
*       GLOBALS :	SBUF, RI
*       RETURN :	SBUF - character received
*        
* PROCESS :
*                   [1]	Wait for character to be received
* 					[2]	Clear RI	       
*					[3] Return character
*/

char getchar () {
	
    while ((SCON & 0x01) == 0);  				// wait for character to be received, spin on RI
	RI = 0;										// clear RI flag
	return SBUF;  								// return character from SBUF
}

/*
* NAME :            putstr
*
* DESCRIPTION :     Print a string to serial port
*
* INPUTS :
*       PARAMETERS:	char *s
*		GLOBALS:	-
*           
* OUTPUTS :
*       GLOBALS :	-
*       RETURN :	Number of characters transmitted
*        
* PROCESS :
*                   [1]  Transmit characters till oxoo (NULL) is encountered        
*/

int putstr (char *s) {
	
	int i = 0;	
	while (*s){									// output characters until NULL found
		putchar(*s++);
		i++;
	}
	//putchar('\n\r');
	return i+1;
}

/*
* NAME :            getstr
*
* DESCRIPTION :     Get a string from serial port
*
* INPUTS :
*       PARAMETERS:	char *s
*		GLOBALS:	-
*           
* OUTPUTS :
*		PARAMETERS: char *s
*       GLOBALS :	-
*       RETURN :	i - number of characters read
*        
* PROCESS :
*                   [1]  Accept characters till 0x0D encountered (CR)
*					[2]	 Store starting at given location (*s)
*
* NOTES :           Is not safe from buffer overrun. Assumes valid memory available for all characters input.
*					Can be substituted with gets() which is equally unsafe but I wrote this so am going with it :)
*/

int getstr (char *s) {
	
	int i = 0;
	do {			
		s[i] = getchar();
		putchar(s[i]);
		i++;
	} while(s[i-1] != 0x0D);  					//0x0D is <CR> 
	s[i-1] = 0x00;								//replace <CR> with NULL (0x00)
	putchar('\n\r');
	return (i-1);
}

/*
* NAME :            serialInit
*
* DESCRIPTION :     Initialize the serial port 
*
* INPUTS :
*       PARAMETERS:	int br (BAUD RATE)
*		GLOBALS:	-
*           
* OUTPUTS :
*       GLOBALS :	PCON, SCON, TMOD, TH1, TR1, TI
*       RETURN :	-
*        
*
* NOTES :  Baud rate is supplied as 0xFF for 56700 etc
*/

void serialInit(int br) {
	
	PCON = 0x80;								/* configure uart, fast baud				*/
	SCON  = 0x50;                   			/* SCON: mode 1, 8-bit UART, enable rcvr    */
	TMOD |= 0x20;                   			/* TMOD: timer 1, mode 2, 8-bit reload      */
	TH1   = br;                   				/* TH1:  reload value for baudrate          */
						//TODO: get load value from real-life baudrate values -- lkup?
	TR1   = 1;                      			/* TR1:  timer 1 run                        */
	TI    = 1;                      			/* TI:   set TI to send first char of UART  */
}

/*
* NAME :            initAll
*
* DESCRIPTION :     Reset global variables to initial values
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	-
*           
* OUTPUTS :
*       GLOBALS :	numBuf, sizeBuf, curBuf, chCount[], stats[]
*       RETURN :	-        
*/

void initAll() {
	
	int i = 0;
	
	numBuf = 0;
	sizeBuf = 0;
	curBuf = 0;
	for (i=0; i<MAXBUFS; i++) {
		chCount[i] = 0;
	}
	for (i=0; i<STATCNT; i++) {
		stats[i] = 0;
	}
}

/*
* NAME :            clearScr
*
* DESCRIPTION :     Clears and resets the VT100 terminal
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	-
*           
* OUTPUTS :
*       GLOBALS :	-
*       RETURN :	-
*        
*
* NOTES :           Will clear the VT100 terminal by sending control sequences to reset and clear entire screen
*					Reset: ESC c
*					Clear Screen: ESC [ 2 J
*/

void clearScr() { 
	printf("\033c");			//reset terminal
	printf("\033[2J");			//clear screen
}

/*
* NAME :            myDelay
*
* DESCRIPTION :     Provide a delay
*
* INPUTS :
*       PARAMETERS:	int j - empirical - j=1 ~ 1 second
*		GLOBALS:	-
*           
* OUTPUTS :
*       GLOBALS :	-
*       RETURN :	-         
*/

void myDelay(int j) {
	int i = 0;
	int cnt = 0;
	
	j*=100;
	while(j) {
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		j--;
	}

}

/*
* NAME :            cleanBuf
*
* DESCRIPTION :     Empty allocated buffer
*
* INPUTS :
*       PARAMETERS:	id - buffer to clean; mode silent/verbose - announce or not
*		GLOBALS:	buffer[], sizeBuf, 
*           
* OUTPUTS :
*       GLOBALS :	buffer[]
*       RETURN :	-
*        
* PROCESS :
*                   [1]  Loop through given buffer and set all to 0x00 (NULL)
*					[2]  Reset associated indexing variable(s)
*
* NOTES :           Wish I could have kept mode as a default parameter :)
*/

void cleanBuf(int id, int mode) {
	
	int i = 0;
	xdata char *ptr = NULL;
	
	ptr = buffer[id];

	if (mode != SILENT) {
		printf_tiny("\n\rCleaning Buffer %d...", id);
	}	
	for (i=0; i<sizeBuf; i++) {
		ptr[i] = 0;
	}
	chCount[id] = 0;
	if (mode != SILENT) {
		putstr("Done\r\n");
	}
}

/*
* NAME :            initBufs
*
* DESCRIPTION :     Clean all allocated buffers
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	numBuf
*           
* OUTPUTS :
*       GLOBALS :	-
*       RETURN :	-
*        
* PROCESS :
*                   [1]  Call cleanBufs() in a loop
*
* NOTES :           Always runs with SILENT because only used at program reset
*/

void initBufs() {
	
	int i = 0;
	
	for (i=0; i<numBuf; i++) {
		cleanBuf(i, SILENT);
	}
}

/*
* NAME :            calcStats
*
* DESCRIPTION :     Count vovels and numeric characters
*
* INPUTS :
*       PARAMETERS:	int id - buffer id for which stats need to be calculated
*		GLOBALS:	buffer[], STATCNT, sizeBuf
*           
* OUTPUTS :
*       GLOBALS :	stats[]
*       RETURN :	-
*        
* PROCESS :
*                   [1]  Initialize stats[]
*					[2]  Traverse buffer and count each type and store globally
*
* NOTES :           enum STAT is used to index into the stats array 
*/

void calcStats(int id) {
	
	int i = 0;
	xdata char *ptr = NULL;
	
	ptr = buffer[id];
	
	for (i=0; i<STATCNT; i++) {					//initialize stats
		stats[i] = 0;
	}
	
	for (i=0; i<sizeBuf; i++) {					//count occurrences
		switch(ptr[i]) {
			case '0': stats[ZERO]++; break;
			case '1': stats[ONE]++; break;
			case '2': stats[TWO]++; break;
			case '3': stats[THREE]++; break;
			case '4': stats[FOUR]++; break;
			case '5': stats[FIVE]++; break;
			case '6': stats[SIX]++; break;
			case '7': stats[SEVEN]++; break;
			case '8': stats[EIGHT]++; break;
			case '9': stats[NINE]++; break;
			case 'a': stats[A]++; break;
			case 'e': stats[E]++; break;
			case 'i': stats[I]++; break;
			case 'o': stats[O]++; break;
			case 'u': stats[U]++; break;
			default: stats[GARBAGE]++;
		}
	}
}

/*
* NAME :            dispAll
*
* DESCRIPTION :     Generate display in required hex or ascii format depending on flag passed
*
* INPUTS :
*       PARAMETERS:	int mode - DISP_ASCII/DISP_HEX; int id - id of buffer to display
*		GLOBALS:	
*           
* OUTPUTS :
*       GLOBALS :	buffer[], chCount[]
*       RETURN :	-
*        
* PROCESS :	
*                   [1]  Loop through given buffer and print with formatting
*
* NOTES :           
*/

void dispAll(int mode, int id) {
	
	int j = 0;
	int idx = 0;
	int modeLim = LIMASCII;
	int tmpAddr = 0;
	int moreDisp = 0;
	xdata char *ptr = NULL;
	
	ptr = buffer[id];
	tmpAddr = (unsigned int)buffer[id];
	//moreDisp = sizeBuf;			//use one of these to display entire buffer or
	moreDisp = chCount[id];			//							  only filled part
	
	printf_tiny("Displaying Buffer %d\n\r", id);	
	if(mode == DISP_HEX) {
		modeLim = LIMHEX;
		putstr("\n\r Address |  +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F");
		putstr("\n\r--------------------------------------------------------------");
	}

	while(moreDisp>0) {
		if(mode == DISP_HEX) 
		printf("\n\r 0x%04X  |  ", tmpAddr);
		for (j=0; (j<modeLim) && moreDisp; j++) {
			if(mode == DISP_HEX) 
			printf("%02X ", ptr[idx]);
			else if (mode == DISP_ASCII) 
			printf("%c", ptr[idx]);
			moreDisp--;
			idx++;
		}
		tmpAddr +=LIMHEX;
		if (mode == DISP_ASCII) 
		putstr("\n\r");
		
	}
	putstr("\n\r");
	ptr = buffer[id];
	
	
	//below is the initial implementation before refactoring - somewhat clunky and inefficient :)
	//keeping here as it is easier to read and as a backup/reference
	
	/*
	if(mode == DISP_ASCII) {
		printf_tiny("Displaying Buffer %d\n\r", id);	
		while(moreDisp>0) {
			for (j=0; (j<50)&&moreDisp; j++) {
				printf("%c", ptr[idx]);
				moreDisp--;
				idx++;
			}
			putstr("\n\r");
		}
		ptr = buffer[id];
	}
	
	
	
	if(mode == DISP_HEX) {
		printf_tiny("Displaying Buffer %d\n\r", id);	
		putstr("\n\r Address |  +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F");
		putstr("\n\r--------------------------------------------------------------");
		while(moreDisp>0) {
			printf("\n\r 0x%04X  |  ", tmpAddr);
			for (j=0; (j<16) && moreDisp; j++) {
				printf("%02x ", ptr[idx]);
				moreDisp--;
				idx++;
			}
			tmpAddr +=16;
		}
		putstr("\n\r");
		ptr = buffer[id];
	}
	*/
	
}

/*
* NAME :            processKey
*
* DESCRIPTION :     Get an input and decide what to do with it
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	bufPtr[] - (indirectly using buffer[]), sizeBuf, chCount[]
*           
* OUTPUTS :
*       GLOBALS :	bufPtr[] - (indirectly using buffer[])
*       RETURN :	PK_QFLAG 		- if '?' received
*					PK_SFLAG 		- if '/' received
*					PK_EINPRINT 	- if non-printable character received
*					PK_EINSTOR		- if buffer is full (will not store)
*					0				- on storing successfully
*        
* PROCESS :
*                   [1]  get and reflect the character
*					[2]  check if character is a command - return appropriately
*					[3]  check if character is printable - return appropriately
*					[4]  check if buffer has space left - return appropriately
*					[5]  if all OK, write to mem and return 0.
*
* NOTES :           TODO: Put in a soft reset command (ESC maybe?)for ease of use (?)
*/

int processKey() {
	
	unsigned char ch = 0;
	
	ch = getchar();
	putchar(ch);
	
	//checking validity before storing
	if (ch == '?') return PK_QFLAG;
	if (ch == '/') return PK_SFLAG;
	if (ch < 0x20 || ch > 0x7E) return PK_EINPRINT;
	
	//valid character, can be stored
	if (chCount[curBuf]>sizeBuf) {
		return PK_EINSTOR;
	}
	bufPtr[chCount[curBuf]] = ch;
	chCount[curBuf]++;
	//printf("stored %c\r\n", bufPtr[chCount[curBuf]]);

	return 0;
}

/*
* NAME :            processQflag
*
* DESCRIPTION :     process the '?' command
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	curBuf, numBuf, sizeBuf, buffer[], chCount[], stats[]
*           
* OUTPUTS :
*       GLOBALS :	buffer[], chCount[]
*       RETURN :	-
*        
* PROCESS :
*                   [1]  Display start address, allocated size and number of printable characters in each buffers
*					[2]  Display stats for vowels and numbers
*					[3]  Generate ASCII dump of current buffer
*					[4]  Erase contents of current buffer
*
* NOTES :           
*/

void processQflag() {
	
	int j = 0;
	
	clearScr();
	calcStats(curBuf);					//this will calculate occurences of vowels and numbers
	
	//Display start address, allocated size and number of printable characters in each buffers
	for(j=0; j<numBuf; j++) {
		printf_tiny("\n\rBUFFER %d: INFORMATION\n\r", j);
		printf("\tStart Address: 0x%04X\n\r", (unsigned int)buffer[j]);
		printf_tiny("\tAllocated Memory: %d bytes\n\r", sizeBuf);
		printf_tiny("\tPrintable characters: %d\n\r", chCount[j]);
		putstr("\n\r");
	}
	
	//Display stats for vowels and numbers
	printf_tiny("\n\r\n\r******* BUFFER %d: STATISTICS *******\n\r\n\r", curBuf);
	
	/*
	for(j=0; j<STATCNT; j++) {		//this will print raw data
		printf("%d ", stats[j]);
	}
	putstr("\n\r");
	*/
	
	putstr("VOWELS:\n\r");
	putstr("\tA\tE\tI\tO\tU\n\r");
	for(j=10; j<15; j++) {
		printf("\t%d ", stats[j]);
	}
	putstr("\n\r");
	putstr("\n\r");
	
	putstr("NUMBERS:\n\r");
	putstr("\t0\t1\t2\t3\t4\t5\t6\t7\t8\t9\n\r");
	for(j=0; j<10; j++) {
		printf("\t%d", stats[j]);
	}
	putstr("\n\r");
	putstr("\n\r");
	
	dispAll(DISP_ASCII, curBuf);
	
	//lastly, empty current buffer
	cleanBuf(curBuf, VERBOSE);
	printf_tiny("\n\r\n\r\n\rBuffer %d > ", curBuf);
}

/*
* NAME :            processSflag
*
* DESCRIPTION :     process the '/' command
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	curBuf, numBuf
*           
* OUTPUTS :
*       GLOBALS :	curBuf - modified for buffer rotation
*       RETURN :	-
*        
* PROCESS :
*                   [1]  Generate hex display in required format
*					[2]  Rotate buffer
*
* NOTES :           
*/

void processSflag() {
	
	clearScr();
	printf_tiny("Current Buffer %d \n\r", curBuf);
	dispAll(DISP_HEX, curBuf);
	
	//lastly, switch to next buffer
	curBuf = ((++curBuf)%numBuf);								//for buffer rotation
	printf_tiny("\n\r\n\r\n\rBuffer %d > ", curBuf);
}

/*=========================================FUNCTION MAIN==========================================*/

void main() {
	
	int rc = 0; 												//general purpose retcode
	
	unsigned char c_numBuf[2] = {0}; 							//1 to 5 plus cr/lf
	unsigned char c_sizeBuf[5] = {0}; 							//20 to 1000 plus cr/lf
	
	unsigned int i = 0;
	unsigned int j = 0;
	volatile unsigned int allocOK = TRUE;

	init_dynamic_memory((MEMHEADER xdata *)heap, HEAP_SIZE);
	serialInit(0xFF); //0xFF gives Baud Rate 57600
	initAll();
	clearScr();
	
	do {
		if (allocOK == FALSE) {		
			myDelay(1);											//insert delay to allow user to see invalid i/p message
		}
		initAll();
		clearScr();
		putstr("Welcome!\n\r\n\r");
		allocOK = TRUE;
		
		if (allocOK == TRUE) {
			putstr("Enter number of buffers to allocate (1-5): ");
			rc = getstr(c_numBuf);
			numBuf = atoi(c_numBuf);
			if (numBuf > MAXBUFS || numBuf < 1) {
				putstr("Invalid input, Try again\n\r");
				allocOK = FALSE;
			}
		}
		
		if (allocOK == TRUE) {
			putstr("Enter size of 1 buffer (20 - 1000 Bytes, Even Number): ");
			rc = getstr(c_sizeBuf);
			sizeBuf = atoi(c_sizeBuf);
			if (sizeBuf > MAXBUFSIZE || sizeBuf < MINBUFSIZE || sizeBuf%2 == 1) {
				putstr("Invalid input, Try again\n\r");
				allocOK = FALSE;
			}
		}
		
		if (allocOK == TRUE) {			
			for (i=0; i<numBuf; i++) {
				buffer[i] = malloc(sizeBuf);
				if (buffer[i] == NULL) {
					printf_tiny("Buffer %d allocation failed\n\r", i);
					for (j=0; j<i; j++) {
						free(buffer[j]);
						printf_tiny("Buffer %d freed\n\r", j);
					}
					allocOK = FALSE;
					break;
				}
				else {
					printf_tiny("Buffer %d allocated\n\r", i);
				}
			}
		}
	}while(allocOK != TRUE);
	
	//buffer allocation complete
	myDelay(1);
	clearScr();
	printf_tiny("====== Allocation Successful - %d buffers %d bytes each ======", numBuf, sizeBuf); 
	bufPtr = buffer[curBuf];						//init bufPtr to point to buffer 0
	initBufs();										//clear all buffers before starting
	
	printf_tiny("\n\r\n\r\n\rBuffer %d > ", curBuf);
	
	while(1){
		bufPtr = buffer[curBuf];
		rc = processKey();
		if(rc == EC_CRIT) break;
		else if(rc == PK_QFLAG) processQflag();
		else if(rc == PK_SFLAG) processSflag();
		else if(rc == PK_EINPRINT) printf_tiny("\n\rUnprintable Character\n\rBuffer %d > ", curBuf);
		else if(rc == PK_EINSTOR); //printf("\n\rBuffer %d is full\n\rBuffer %d > ", curBuf, curBuf); //silent discard
	}
	
	putstr("\n\r\n\rCritical Error!!!\n\r");		//should never have reached here
}

